#!/usr/bin/python
#
# Copyright 2011 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

"""Keyboard Click generator
Press a key and get an audible click.
"""

__author__ = 'Scott Kirkwood (scott+keyclick@forusers.com)'
__version__ = '0.1.3'

# Options must appear early because of confusion with gtk options.
import options

import pygtk
pygtk.require('2.0')
import gobject
import gst
import gtk
import gettext
import logging
import os
import settings_dialog
import sys
import threading
import time
import xlib

gettext.install('key-click', 'locale')

DEFAULT_AUDIO_FNAME = '406__TicTacShutUp__click_1_d.wav'

class KeyClick:
    """main KeyClick window class."""

    def __init__(self):
        """Create the Key Click window.
        """
        self.status_icon = None
        self.click_thread = None
        self.create_options()
        self.opts.read_ini_file(os.path.join(self.get_config_dir(), 'config'))

    def get_config_dir(self):
        return os.environ.get('XDG_CONFIG_HOME',
                os.path.expanduser('~/.config')) + '/key-click'

    def create_options(self):
        self.opts = options.Options()
        self.opts.add_option(
                opt_long='--volume', dest='volume', default=50, type='int',
                ini_group='audio', ini_name='volume',
                help=_('Volume from [0-100].'))
        self.opts.add_option(
                opt_short='-s', opt_long='--sound-file', dest='sound_file',
                default=DEFAULT_AUDIO_FNAME, type='str',
                ini_group='audio', ini_name='sound_file',
                help=_('Filename of sound file to use.'))
        self.opts.add_option(
                opt_short='-v', opt_long='--version', dest='version',
                type='bool', help=_('Show version information and exit.'))
        self.opts.add_option(opt_short='-d', opt_long='--debug', dest='debug', type='bool',
                default=False,
                help=_('Output debugging information. '
                       'Shorthand for --loglevel=debug'))

    def command_line(self, args):
        desc = _('Usage: %prog [Options...]')
        self.opts.parse_args(desc, args)
        if self.opts.version:
            self.show_version()
            sys.exit(0)
        if self.opts.debug:
            loglevel = logging.DEBUG
            logging.basicConfig(
                    level=loglevel,
                    format='%(filename)s [%(lineno)d]: %(levelname)s %(message)s')

    def start(self):
        # Required to get threading to work
        gobject.threads_init()

        # create a new Status Icon
        self.status_icon = gtk.StatusIcon()
        self.status_icon.set_from_stock(gtk.STOCK_ABOUT)
        self.status_icon.connect("activate", self.left_click_event)
        self.status_icon.connect("popup-menu", self.right_click_event)

        self.click_thread = ClickThread(self)
        self.create_window()
        self.click_thread.start()

        try:
            gtk.main()
        except KeyboardInterrupt:
            print('Quit program')
            self.quit_program()

    def left_click_event(self, unused_widget):
        pass

    def right_click_event(self, unused_icon, button, cur_time):
        self.menu = gtk.Menu()

        about = gtk.MenuItem(_("About..."))
        settings = gtk.MenuItem(_('_Settings...'))
        quit_menu = gtk.MenuItem(_("Quit"))

        about.connect("activate", self.show_about_dialog)
        settings.connect("activate", self.show_settings_dialog)
        quit_menu.connect("activate", self.quit_program)

        self.menu.append(about)
        self.menu.append(settings)
        self.menu.append(quit_menu)

        self.menu.show_all()

        self.menu.popup(None, None, None, button, cur_time)

    def create_window(self):
        """Create the main window."""
        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.set_title('Keyboard Click Generator')
        self.window.connect('destroy', self.destroy)

    def show_about_dialog(self, unused_widget):
        about_dialog = gtk.AboutDialog()

        about_dialog.set_destroy_with_parent(True)
        about_dialog.set_name("Key-click")
        about_dialog.set_version(__version__)
        about_dialog.set_authors([__author__])

        about_dialog.run()
        about_dialog.destroy()

    def show_settings_dialog(self, *unused_args):
        """Show the settings dialog."""
        dlg = settings_dialog.SettingsDialog(self.window, self.opts)
        dlg.connect('settings-changed', self.settings_changed)
        dlg.show_all()
        dlg.run()
        dlg.destroy()

    def settings_changed(self, unused_widget):
        self.click_thread.setup_audio()

    def show_version(self):
        """Show the version number and author, used by help2man."""
        print(_('Key-click version %s.') % __version__)
        print(_('Written by %s') % __author__)

    def quit_program(self, *unused_args):
        """Quit the program."""
        if self.click_thread:
          self.click_thread.quit()
          self.click_thread = None
        self.destroy(None)
        sys.exit(0)

    def destroy(self, unused_widget, unused_data=None):
        """Also quit the program."""
        if self.click_thread:
          self.click_thread.quit()
          self.click_thread = None
        self.opts.save()


class ClickThread(threading.Thread):
    def __init__(self, parent):
        super(ClickThread, self).__init__()
        self.loop = True
        self.parent = parent
        self.events = xlib.XEvents()
        self.events.start()
        self.setup_audio()

    def run(self):
        event = None
        try:
            while self.loop and self.events.listening():
                event = self.events.next_event()
                if event:
                    self.handle_event(event)

                # Give the operating system a breather
                time.sleep(0.005)
        finally:
            self.shutdown()

    def setup_audio(self):
        self.player = gst.element_factory_make("playbin", "player")
        fname = os.path.join(
                os.path.abspath(os.path.dirname(__file__)),
                self.parent.opts.sound_file)
        self.player.set_property("uri", 'file://' + fname)

        # Cache the sound
        self.player.set_property("volume", 0)
        self.player.set_state(gst.STATE_PLAYING)
        self.player.set_state(gst.STATE_NULL)

        volume = self.parent.opts.volume
        self.player.set_property("volume", volume / 100.0)

    def handle_event(self, event):
        """Handle an X event."""
        if event.type == 'EV_KEY' and event.value == 1:
            if type(event.code) == str:
                if event.code.startswith('KEY'):
                    code_num = event.scancode
                    self.handle_key(code_num, event.code, event.value)
                elif event.code.startswith('BTN'):
                    self.handle_mouse_button(event.code, event.value)

    def handle_key(self, unused_scan_code, unused_xlib_name, unused_value):
        """Handle a keyboard event."""
        self.player.set_state(gst.STATE_NULL)
        self.player.set_state(gst.STATE_PLAYING)

    def handle_mouse_button(self, unused_code, unused_value):
        """Handle the mouse button event."""
        pass

    def shutdown(self):
        self.events.stop_listening()
        self.events = None
        self.player = None
        print('Exiting thread')

    def quit(self):
        self.loop = False


def main():
    """Run the program."""
    keyclick = KeyClick()
    keyclick.command_line(sys.argv)
    keyclick.start();


if __name__ == '__main__':
    main()
